# !/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time : 2023/4/23 21:43
# @Author : xyjiang1216
# @Email : jiangxuyan@nudt.edu.cn
# @File : construct_topo.py
# @Version: OpenPlanner2.0
import importlib
from abc import ABCMeta, abstractmethod
from random import choice

from OpenPlanner.core.src.op_class import OPClass, TopoParam
from OpenPlanner.core.src.topo_construction_strategy.construct_linear_topo_graph import LinearTopoStrategy
from OpenPlanner.core.src.topo_construction_strategy.construct_ring_topo_graph import RingTopoStrategy
from OpenPlanner.core.src.topo_construction_strategy.construct_snowflake_topo_graph import SnowflakeTopoStrategy
from OpenPlanner.debug.debug_utils import DEBUG_PRINT
from OpenPlanner.util.json_utils import write_json
from OpenPlanner.util.topo_graph_utils import read_graph


class TopoConstructionModule:
    def __init__(self,
                 op_obj: OPClass):
        self.op_obj = op_obj
        # self.strategy = None
        # self.graph = None
        self.topo_hook_func = None

    def _construct_topo_graph_from_param(self):
        # 1. 获取所需要使用的策略
        topo_type = self.op_obj.topo_type

        # 2. 生成该策略类的对象
        if topo_type == 'LINEAR':
            self.strategy = LinearTopoStrategy(
                self.op_obj.topo_param
            )
        elif topo_type == 'RING':
            self.strategy = RingTopoStrategy(
                self.op_obj.topo_param
            )
        elif topo_type == 'SNOWFLAKE':
            self.strategy = SnowflakeTopoStrategy(
                self.op_obj.topo_param
            )
        # 3. 调用策略的接口，即construct_topo_graph
        graph = self.strategy.generate_topo_graph()
        return graph

    def _construct_topo_graph_from_file(self):
        topo_type = self.op_obj.topo_type
        filepath = ''
        if topo_type == 'MIURA':
            filepath = '../res/miura_topo.json'
        elif topo_type == 'TRAIN':
            filepath = '../res/train_topo.json'
        elif topo_type == 'AUTOMOTIVE':
            filepath = '../res/automotive_topo.json'
        elif topo_type == 'ORION':
            filepath = '../res/orion_CEV_topo.json'

        graph = read_graph(filepath)
        return graph

    def _random_topo_param(self, graph):
        # graph = self.graph

        speed_set = self.op_obj.topo_param.speed_set_per_link
        st_queues_set = self.op_obj.topo_param.st_queue_set_per_link
        user_defined_param = self.op_obj.topo_param.user_defined_param

        link_set = []
        links = graph.edges
        for per_link in links:
            src_node = per_link[0]
            dst_node = per_link[1]
            link_id = graph[src_node][dst_node]['link_id']
            try:
                speed = choice(speed_set)
                st_queues = choice(st_queues_set)
            except IndexError:
                print('配置文件中的speed_set和st_queues_set'
                      '不能为空')
                exit(0)
            link = {}
            try:
                if self.op_obj.topo_param.port_flag == 'True':
                    link = {'link_id': link_id, 'src_node': src_node,
                            'src_port': graph[src_node][dst_node]['src_port'],
                            'dst_node': dst_node,
                            'dst_port': graph[src_node][dst_node]['dst_port'],
                            'speed': speed,
                            'st_queues': st_queues
                            }
                elif self.op_obj.topo_param.port_flag == 'False':
                    link = {'link_id': link_id, 'src_node': src_node,
                            'dst_node': dst_node, 'speed': speed,
                            'st_queues': st_queues
                            }
            except:
                # TODO：
                # 给下述四种拓扑添加端口号信息
                print('MIURA、TRAIN、AUTOMOTIVE、ORION拓扑'
                      '暂时没有端口号信息，请修改port_flag为False')
                exit(0)
            # 判断是否有自定义参数，
            # 若有，添加自定义参数
            for item in user_defined_param:
                try:
                    link.update({item: choice(user_defined_param[item])})
                except IndexError:
                    print('配置文件topo_param参数中'
                          'user_defined_param的%s'
                          '不能为空！' % item)
                    exit(0)
            link_set.append(link)
        link_set.sort(key=lambda x: x['link_id'])

        return link_set

    def construct_topo_json(self):
        """

        :return:
        """

        '''生成拓扑图'''
        topo_type = self.op_obj.topo_type
        graph = None
        if topo_type == 'CUSTOM':
            try:
                fd = open(self.op_obj.topo_filepath,
                          'r')
            except IOError:
                print("Error: 用户自定义拓扑文件不存在或无法读取该文件")
                exit(0)
            else:
                fd.close()
                return
        elif topo_type == 'MIURA' or topo_type == 'TRAIN' or topo_type == 'AUTOMOTIVE' or topo_type == 'ORION':
            graph = self._construct_topo_graph_from_file()
        elif topo_type == 'LINEAR' or topo_type == 'RING' or topo_type == 'SNOWFLAKE':
            graph = self._construct_topo_graph_from_param()
        else:
            print('Error:'
                  'OpenPlanner无法生成该类型拓扑(%s)，'
                  '请从OpenPlanner支持的拓扑中选择。'
                  'OpenPlanner支持的拓扑包括：MIURA、TRAIN、AUTOMOTIVE、'
                  'ORION、LINEAR、RING、SNOWFLAKE'
                  % topo_type)
            exit(0)

        '''随机拓扑参数'''
        link_set = self._random_topo_param(graph)

        '''写拓扑文件'''
        write_json(link_set,
                   self.op_obj.topo_filepath)

    '''钩子函数相关'''

    def _register_topo_hook(self, func):
        self.topo_hook_func = func

    def custom_topo_hook(self):
        """拓扑构建层的钩子"""
        base_module = self.op_obj.topo_hook.base_module_path
        func_name = self.op_obj.topo_hook.hook_func_name
        if base_module != '' and func_name != '':
            # 注册钩子
            # 判断模块是否存在
            try:
                module = importlib.import_module(base_module)
            except ModuleNotFoundError:
                print("ModuleNotFoundError:"
                      "拓扑钩子函数的%s模块不存在，"
                      "请检查钩子函数路径后更新配置文件" % base_module)
                exit(0)
            # 判断某个模块中是否存在某个函数
            try:
                func = getattr(module, func_name)
            except AttributeError:
                print("AttributeError:"
                      "模块%s内没有函数%s，"
                      "请检查拓扑钩子函数名称后更新配置文件"
                      % (base_module, func_name))
                exit(0)
            self._register_topo_hook(func)

        # 执行钩子函数
        if self.topo_hook_func is not None:
            kwargs = self.op_obj.topo_hook.hook_func_param
            self.topo_hook_func(self.op_obj, **kwargs)
        else:
            DEBUG_PRINT("No hook after topo construction")
            pass
